/** memortstream.d - Memory stream
Authors: 
	Wei Li (oldrev@gmail.com)
	and below....

License:
	BSD

Copyright:
	See below

*/

// This file is a port from Mono corlib by Wei Li

// System.IO.MemoryStream 
//
// Authors:	Marcin Szczepanski (marcins@zipworld.com.au)
//		Patrik Torstensson
//		Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (c) 2001,2002 Marcin Szczepanski, Patrik Torstensson
// (c) 2003 Ximian, Inc. (http://www.ximian.com)
// Copyright (C) 2004 Novell (http://www.novell.com)
//

//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

module dotmars.io.memorystream;

public import dotmars.io.stream;

public class MemoryStream : Stream
{
	private bool m_writable;
	private bool m_allowGetBuffer;
	private size_t m_capacity;
	private size_t m_length;
	private ubyte[] m_internalBuffer;
	private size_t m_initialIndex;
	private bool m_expandable;
	private bool m_streamClosed;
	private size_t m_position;

	public alias Stream.read read;
	public alias Stream.write write;

	public this () 
	{
		this(0);
	}

	public this (size_t cap)
	{
		m_writable = true;

		m_capacity = cap;
		m_internalBuffer = new ubyte [m_capacity];

		m_expandable = true;
		m_allowGetBuffer = true;
	}

	public this (ubyte[] buffer)
	{
		internalConstructor (buffer, 0, buffer.length, true, false);                        
	}

	public this (ubyte[] buffer, bool writeable)
	{
		internalConstructor (buffer, 0, buffer.length, writeable, false);
	}

	public this (ubyte [] buffer, size_t index, size_t count)
	{
		internalConstructor (buffer, index, count, true, false);
	}

	public this (ubyte [] buffer, size_t index, size_t count, bool writeable)
	{
		internalConstructor (buffer, index, count, writeable, false);
	}

	public this (ubyte [] buffer, size_t index, size_t count, bool writeable, bool publicallyVisible)
	{
		internalConstructor (buffer, index, count, writeable, publicallyVisible);
	}

	private void internalConstructor (ubyte [] buffer, size_t index, size_t count, bool writeable, bool publicallyVisible)
	in
	{
		assert(buffer !is null);
		assert(buffer.length -index >= count);
	}
	body
	{
		m_writable = writeable;

		m_internalBuffer = buffer;
		m_capacity = count + index;
		m_length = m_capacity;
		m_position = index;
		m_initialIndex = index;

		m_allowGetBuffer = publicallyVisible;
		m_expandable = false;                
	}

	public ~this()
	{
		close();
	}

	private void checkIfClosedThrowDisposed ()
	{
		if (m_streamClosed)
			throw new IOException ("MemoryStream");
	}

	private void checkIfClosedThrowIO ()
	{
		if (m_streamClosed)
			throw new IOException ("MemoryStream is closed");
	}

	public override bool readable() {
		return !m_streamClosed;
	}

	public override bool writable(){
		return !m_streamClosed; 
	}

	public override bool seekable(){
		return (!m_streamClosed && m_writable);
	}

	public size_t capacity() 
	{
		checkIfClosedThrowDisposed ();
		return m_capacity - m_initialIndex;

	}

	public void capacity(size_t value)
	in
	{
		assert(m_expandable);
		assert(value >= m_length);
	}
	body
	{
		checkIfClosedThrowDisposed ();
		if (value == m_capacity)
			return; // LAMENESS: see MemoryStreamTest.ConstructorFive

		ubyte [] newBuffer = null;
		if (value > 0) 
		{
			newBuffer = new ubyte [value];

			newBuffer[0..m_length] = m_internalBuffer[0..m_length];
		}

		m_internalBuffer = newBuffer; // It's null when m_capacity is set to 0
		m_capacity = value;
	}

	public override long length() 
	{
		checkIfClosedThrowDisposed();
		return m_length - m_initialIndex;
	}

	public override long position() 
	{
		checkIfClosedThrowDisposed ();
		return m_position - m_initialIndex;
	}

	public override void position(long value)
	in
	{
		assert(value >= 0);
	}
	body
	{
		checkIfClosedThrowDisposed ();
		m_position = m_initialIndex + cast(int) value;
	}

	public override void close ()
	{
		m_streamClosed = true;
		m_expandable = false;
	}

	public override void flush ()
	{
		// Do nothing
	}

	public ubyte [] getBuffer ()
	in {
		assert(m_allowGetBuffer);
	}
	body {
		return m_internalBuffer;
	}

	public override size_t read (void *data, size_t count)
	in {
		assert(data !is null);
	}
	body {
		checkIfClosedThrowDisposed ();

		ubyte* buffer = cast(ubyte*)data;

		if (m_position >= m_length || count == 0) return 0;

		if (m_position + count > m_length)
			count = m_length - m_position;
		buffer[0 .. count] = m_internalBuffer[m_position .. m_position + count];
		m_position += count;
		return count;
	}

	public override int readByte ()
	{
		checkIfClosedThrowDisposed ();
		if (m_position >= m_length) return -1;

		return m_internalBuffer [m_position++];
	}

	public override long seek (long offset, SeekOrigin loc)
	in
	{
		assert(offset < cast(long)int.max);
	}
	body
	{
		checkIfClosedThrowDisposed ();

		int refPoint;
		switch (loc) {
			case SeekOrigin.Begin:
				if (offset < 0)
					throw new IOException ("Attempted to seek before start of MemoryStream.");
				refPoint = m_initialIndex;
				break;

			case SeekOrigin.Current:
				refPoint = m_position;
				break;

			case SeekOrigin.End:
				refPoint = m_length;
				break;

			default:
				assert(false);
				break;
		}

		// LAMESPEC: My goodness, how may LAMESPECs are there in this
		// class! :)  In the spec for the Position property it's stated
		// "The m_position must not be more than one byte beyond the end of the stream."
		// In the spec for seek it says "Seeking to any location beyond the m_length of the 
		// stream is supported."  That's a contradiction i'd say.
		// I guess seek can go anywhere but if you use m_position it may get moved back.

		refPoint += cast(int) offset;
		if (refPoint < m_initialIndex)
			throw new IOException ("Attempted to seek before start of MemoryStream.");

		m_position = refPoint;
		return m_position;
	}

	private size_t calculateNewCapacity (size_t minimum)
	{
		if (minimum < 256) minimum = 256; 

		if (minimum < m_capacity * 2)
			minimum = m_capacity * 2;

		return minimum;
	}

	public override void length(long value)
	in
	{
		assert(!(!m_expandable && value > m_capacity));
		assert(m_writable);
		assert(!(value < 0 || (value + m_initialIndex) > cast(long) size_t.max));
	}
	body
	{
		checkIfClosedThrowDisposed ();

		size_t newSize = cast(size_t) value + m_initialIndex;
		if (newSize > m_capacity)
			capacity = calculateNewCapacity (newSize);
		
		/*else if (newSize < m_length)
			// zeroize present data (so we don't get it 
			// back if we expand the stream using Seek)
			Array.Clear (m_internalBuffer, newSize, m_length - newSize);*/

		m_length = newSize;
		if (m_position > m_length)
			m_position = m_length;
	}

	public ubyte[] toArray ()
	in
	{
		assert(m_internalBuffer !is null);
		assert(m_internalBuffer.length >= m_length);
	}
	body
	{
		return m_internalBuffer[m_initialIndex .. m_length].dup;
	}

	public override void write (void* data, size_t count)
	in
	{
		assert(m_writable);
		assert(data !is null);
	}
	body
	{
		checkIfClosedThrowDisposed ();
		ubyte* buffer = cast(ubyte*)data;

		// reordered to avoid possible integer overflow
		if (m_position + count > m_capacity)
			capacity = calculateNewCapacity (m_position + count);
		m_internalBuffer[m_position .. m_position + count] = buffer[0.. count];

		m_position += count;
		if (m_position >= m_length)
			m_length = m_position;
	}

	public override void writeByte (ubyte value)
	in
	{
		assert(writable);
	}
	body
	{
		checkIfClosedThrowDisposed ();

		if (m_position >= m_capacity)
			capacity = calculateNewCapacity (m_position + 1);

		if (m_position >= m_length)
			m_length = m_position + 1;

		m_internalBuffer [m_position++] = value;
	}

	public void writeTo (Stream stream)
	in {
		assert(stream !is null);
	}
	body
	{
		checkIfClosedThrowDisposed ();

		stream.write (m_internalBuffer[m_initialIndex .. m_length]);
	}

	public override ubyte opIndex(long pos)
	in {
		assert(pos >= 0 && pos < m_length);
	}
	body {
		return m_internalBuffer[pos];
	}

	public override ubyte opIndexAssign(ubyte rhs, long pos)
	in {
		assert(pos >= 0 && pos < m_length);
	}
	body {
		return m_internalBuffer[pos] = rhs;
	}

	public override void[] opSlice() {
		//For consistency
		return m_internalBuffer[0..m_length].dup;
	}

	public override void[] opSlice(long start, long end)
	in {
		assert(start < end);
		assert(start >= 0);
		assert(end <= m_length);
	}
	body {
		return m_internalBuffer[start..end].dup;
	}

}            
